package mini;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.StringTokenizer;

import mini.compiler.CodeGenerator;
import mini.compiler.Linker;
import mini.compiler.Optimizer;

public class Program {

    private StatementList s;

    public static final String NEWLINE = System.getProperty("line.separator");
    
    private static final String DEFAULT_SYMBOLIC_FILENAME = "resources/output.unopt.sym";
    private static final String DEFAULT_OPT_PROG_FILENAME = "resources/output.opt.sym";
    private static final String DEFAULT_UNOPT_LINKED_PROG_FILENAME = "resources/output.unopt.lnk";
    private static final String DEFAULT_LINKED_PROG_FILENAME = "resources/output.opt.lnk";
    private static final String DEFAULT_LINKED_MEM_FILENAME = "resources/output.opt.mem";
    private static final String DEFAULT_UNOPT_LINKED_MEM_FILENAME = "resources/output.unopt.mem";
    private static final String END_OF_SOURCE_CODE = "Dumping out all the variables...";

    public Program(StatementList list){
    	s = list;
    }

    public void eval(HashMap hm, LinkedList var){
    	s.eval(hm, var);
    }

    public void parse(HashMap hm, LinkedList var) {
    	
    	// First we do some parsing to generate the symbolic code
    	String output = s.parse(hm, var);
    	
    	// Make sure to add a HLT instruction at the end!
    	CodeGenerator cg = new CodeGenerator();
    	cg.appendCode( output );
    	cg.addHalt();
    	output = cg.getCode();
    	
    	// And write the symbolic code out to a file
    	writeSymbolicCode(output);
    	
    	// And the second optimizing step
    	optimize();

    	// This does the final step of linking
    	link(hm);
    }

    /**
     * Linking is done by reading in the symbolic file that was written previously,
     * opening some output files, and passing it off to the linker
     * @param hm Maps constants to variable names, which we need for 
     *           setting up memory. These start with 'Constant' as their
     *           name.
     */
	private void link( HashMap hm ) {
		// Now call the linker to get the linked code written out
    	BufferedReader inputReader = null;
    	BufferedWriter progWriter = null;
    	BufferedWriter memWriter = null;
		try {
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_OPT_PROG_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_LINKED_PROG_FILENAME)) );
	    	memWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_LINKED_MEM_FILENAME)) );
	    	(new Linker()).link(hm, inputReader, memWriter, progWriter);
	    	
	    	inputReader.close();
	    	progWriter.close();
	    	memWriter.close();
	    	
	    	// Now write the unoptimized version
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_SYMBOLIC_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_UNOPT_LINKED_PROG_FILENAME)) );
	    	memWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_UNOPT_LINKED_MEM_FILENAME)) );
	    	(new Linker()).link(hm, inputReader, memWriter, progWriter);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if( inputReader != null ) { 
				try {
					inputReader.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if( progWriter != null ) { 
				try {
					progWriter.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if( memWriter != null ) { 
				try {
					memWriter.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
 
    // Optimizing is done by reading in the unoptimized memory and program files,
    // opening some output files, and passing it off to the optimizer
	private void optimize() {
		// Now call the optimizer to get the optimized code written out
    	BufferedReader progReader = null;
    	BufferedWriter progWriter = null;
		try {
			progReader = new BufferedReader( new FileReader( new File(DEFAULT_SYMBOLIC_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_OPT_PROG_FILENAME)) );
	    	(new Optimizer()).optimize(progReader, progWriter);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if( progReader != null ) { 
				try {
					progReader.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if( progWriter != null ) { 
				try {
					progWriter.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
 
    /**
     * This is the first stage of compiling. It's generated symbolic code,
     * but not linked or optimized.
     * @param output
     */
    private void writeSymbolicCode(String output) {
    	
    	try {
			BufferedWriter writer = new BufferedWriter( new FileWriter( new File(DEFAULT_SYMBOLIC_FILENAME) ) );
			StringTokenizer strtok = new StringTokenizer( output, NEWLINE );
			boolean keepGoing = true; 
			while( strtok.hasMoreTokens() && keepGoing ) {
				String line = strtok.nextToken().trim();
				if( line.equals(Program.END_OF_SOURCE_CODE) ) {
					keepGoing = false;
				} else if( line.trim().length() > 0 ) {
					writer.write( line );
					// If the last line we read was a label, we don't add a newline; this is so
					// two consecutive lines of:
					// loc3:
					// HLT
					// Actually get written as:
					// loc3: HLT
					if( isLocationLabel(line) ) {
						writer.write( " " );	// We do need to space after the label
					} else {
						writer.write( NEWLINE );
					}
				}
			}
			writer.close();
		} catch (IOException e) {
			System.err.println( "Could not create output symbolic file to: " + DEFAULT_SYMBOLIC_FILENAME );
			e.printStackTrace();
			System.exit(1);
		}
	}

    /**
     * What identifies a location label is that it ends with :
     * @param line The trimmed line
     * @return True if it's a location label, false otherwise
     */
	private boolean isLocationLabel(String line) {
		return line.endsWith(":");
	}

	public void dump(HashMap hm, LinkedList var){
		//System.out.println(hm.values());
		int n = var.size();
		int i;
		String s;
		System.out.println("Dumping out all the variables...");
		for (i=0; i<n; i++){
		    s = (String)var.get(i);
		    System.out.println(s + "=" + hm.get(s).toString());
		}
    }
}
